package org.noote.libs.computervision.buffer;

import org.noote.libs.computervision.function.Kernel;
import org.noote.libs.computervision.math.Color8;

public class Buffer8I extends Buffer2D {

	// yes 'short' for a value from 0 to 255 (lack of unsigned support from java :()
	protected short [] _i8I = null;

	@Override
	public boolean createBuffer(int width, int height)
	{
		if(_width != width || _height != height)
		{
			if(!super.createBuffer(width, height)) return false;
			
			this._i8I = new short[_width*_height];
		}
		
	    return true;
	}
	
	public boolean createAs(Buffer8I from)
	{
		if(createBuffer(from.getWidth(), from.getHeight()))
		{
			return true;
		}
		return false;
	}

	public boolean createFrom(Buffer8I from)
	{
		if(createAs(from))
		{
			putBuffer(0, from.getBuffer());
			return true;
		}
		return false;
	}

	public boolean exportTo(Buffer8888XRGB xrgb)
	{
		if(xrgb.getWidth() != _width || xrgb.getHeight() != _height)
		{
			if(!xrgb.createBuffer(_width, _height)) return false;
		}
		
		// parcours le buffer
		for(int i=0; i<_width*_height; i++)
		{
			int c = _i8I[i];
		
			// put as grey
			xrgb.putPixel(i, (c<<16)|(c<<8)|c);
		}

		return true;
	}
	
	public boolean exportAsColoredTo(Buffer8888XRGB xrgb, Color8 color)
	{
		return exportAsColoredTo(xrgb, color.red, color.green, color.blue);
	}
	public boolean exportAsColoredTo(Buffer8888XRGB xrgb, short iRed, short iGreen, short iBlue)
	{
		if(xrgb.getWidth() != _width || xrgb.getHeight() != _height)
		{
			if(!xrgb.createBuffer(_width, _height)) return false;
		}
		
		// Invert value
		float fFactorRed=(float)iRed/255.0f, fFactorGreen=(float)iGreen/255.0f, fFactorBlue=(float)iBlue/255.0f; 
		
		// parcours le buffer
		for(int i=0; i<_width*_height; i++)
		{
			int r = (int)(_i8I[i]*fFactorRed);
			int g = (int)(_i8I[i]*fFactorGreen);
			int b = (int)(_i8I[i]*fFactorBlue);
			
			if(r>255) r=255;
			if(g>255) g=255;
			if(b>255) b=255;
		
			// put as grey
			xrgb.putPixel(i, (r<<16)|(g<<8)|b);
		}

		return true;
	}

	public short[] getBuffer()
	{
		return _i8I;
	}
	
	@Override
	public boolean isVoid()
	{
		return (super.isVoid() || _i8I==null);
	}
	
	@Override
	public void putPixel(int offset, int c)
	{
		if(offset>-1 && offset<_width*_height)
			_i8I[offset] = (short)(c&0xFF);
	}
    public void putBuffer(int offset, short [] buffer)
    {
		if(offset<0 || offset>=_width*_height) return;
    	int len = (offset+buffer.length>_width*_height)?(_width*_height-buffer.length-1):buffer.length;
    	for(int i=0; i<len; i++)
    		_i8I[offset+i] = buffer[i];
    }
	@Override
	public int getPixel(int x, int y)
	{
		if(x>-1 && x<_width && y>-1 && y<_height && _i8I!=null)
			return _i8I[(y*_width)+x];
	
		return -1;
	}
	@Override
	public void drawPixel(int x, int y, int c)
	{
		if(x>-1 && x<_width && y>-1 && y<_height && _i8I!=null)
			_i8I[(y*_width)+x] = (short)(c&0xFF);
	}
	
	protected void binaryThreshold(short iMed)
	{
		binaryThreshold(iMed, (short)0, (short)255);
	}
	protected void binaryThreshold(short iMed, short iLow, short iHigh)
	{
		iMed = iMed > 255 ? 255 : iMed < 0 ? 0 : iMed;
		
		for(int i=0; i>_width*_height; i++)
		{
			if(_i8I[i]>iMed) _i8I[i] = iHigh;
			else _i8I[i] = iLow;
		}
	}

	protected void threshold(short iThreshold)
	{
		threshold(iThreshold, iThreshold, (short)0, (short)255);
	}
	protected void threshold(short iThresholdMin, short iThresholdMax)
	{
		threshold(iThresholdMin, iThresholdMax, (short)0, (short)255);
	}
	protected void threshold(short iThresholdMin, short iThresholdMax, short iValueLow, short iValueHigh)
	{
		iThresholdMax = iThresholdMax > 255 ? 255 : iThresholdMax < 0 ? 0 : iThresholdMax;
		iThresholdMin = iThresholdMin > iThresholdMax ? iThresholdMin = iThresholdMax : iThresholdMin < 0 ? 0 : iThresholdMin;
		
		for(int i=0; i>_width*_height; i++)
		{
			if(_i8I[i]>=iThresholdMax) _i8I[i] = iValueHigh>iThresholdMax?iValueHigh:iThresholdMax;
			else if(_i8I[i]<=iThresholdMin) _i8I[i] = iValueLow<iThresholdMin?iThresholdMin:iValueLow;
		}
	}

	protected void add(short iAdd)
	{
		for(int i=0; i>_width*_height; i++)
		{
			short iVal = _i8I[i];
			
			iVal += iAdd;
			
			iVal = iVal > 255 ? 255 : iVal < 0 ? 0 : iVal;
			
			_i8I[i] = iVal;
		}
	}
	
	protected void sub(short iSub)
	{
		add((short)-iSub);
	}

	protected void mult(float fFactor)
	{
		for(int i=0; i>_width*_height; i++)
		{
			short iVal = _i8I[i];
			
			iVal *= fFactor;
			
			iVal = iVal > 255 ? 255 : iVal < 0 ? 0 : iVal;
			
			_i8I[i] = iVal;
		}
	}

	protected void div(float fFactor)
	{
		mult(1.0f/fFactor);
	}

	protected int [] getHistogram()
	{
		// histogramme brut
		int [] iHisto = new int [256];
		for(int idx=0; idx<_width*_height; idx++)
		{
			iHisto[_i8I[idx]]++;
		}
		
		return iHisto;
	}

	protected void doFlatHistogram()
	{
		// histogramme brut
		int [] iHisto = getHistogram();

		// recherche des maxima "notables"
		int low = 0, high = 0xFF;
		for(int h=0; h<254; h++)
		{
			if(Math.abs(iHisto[h]-iHisto[h+1])>25)
			{
				low = h;
				break;
			}
		}
		for(int h=255; h>1; h--)
		{
			if(Math.abs(iHisto[h]-iHisto[h-1])>25)
			{
				high = h;
				break;
			}
		}
		if(low==0 && high == 255) return;

		// correction
		float fCoef = 1.0f+(1.0f-((high-low)/255.0f));
		for(int idx=0; idx<_width*_height; idx++)
		{
			short i = _i8I[idx];
			
			short r = (short) ((i-low)*fCoef);
			if(r>0xFF) r=0xFF;
			if(r<0) r=0;
			
			_i8I[idx] = r;  
		}
	}

	public int [] getVerticalStats(short iThreshold)
	{
		int [] iStats = new int [_height];
		
		int idx;
		for(int x=0; x<_width; x++)
		{
			for(int y=0; y<_height; y++)
			{
				idx = y*_width+x;
				
				if(_i8I[idx]>iThreshold)
					iStats[y]++;
			}
		}
		
		return iStats;		
	}
	
	public int [] getHorizontalStats(short iThreshold)
	{
		int [] iStats = new int [_width];
		int idx;
		for(int y=0; y<_height; y++)
		{
			for(int x=0; x<_width; x++)
			{
				idx = y*_width+x;
				
				if(_i8I[idx]>iThreshold)
					iStats[x]++;
			}
		}
		
		return iStats;		
	}

	public boolean add(Buffer8I buffer)
	{
		if(buffer.getWidth() != getWidth() || buffer.getHeight() != getHeight())
			return false;

		int iTemp;
		for(int i=0; i<_width*_height; i++)
		{
			iTemp = _i8I[i]+buffer._i8I[i];

			_i8I[i] = (short)(iTemp>0xFF?0xFF:iTemp);
		}

		return true;
	}

	public boolean diff(Buffer8I buffer)
	{
		if(buffer.getWidth() != getWidth() || buffer.getHeight() != getHeight())
			return false;

		int iTemp;
		for(int i=0; i<_width*_height; i++)
		{
			iTemp = Math.abs(_i8I[i]-buffer._i8I[i]);

			_i8I[i] = (short)(iTemp>0xFF?0xFF:iTemp);
		}

		return true;
	}
	
	public void negative()
	{
		for(int i=0; i<_width*_height; i++)
		{
			_i8I[i] = (short)(255-_i8I[i]);
		}		
	}
	
	protected boolean erode(int iEw, int iEh)
	{
		int w = iEw, h= iEh;

		if (w < 2) w = 2;
		if (h < 2) h = 2;

		int strel_w = w;
		int strel_h = h;

		short val, val2;

		Buffer8I trg = new Buffer8I();
		if(!trg.createFrom(this))
			return false;

		for(int y=0; y<_height; y++)
		{
			for(int x=0; x<_width; x++)
			{
				val = 255;
				for (int j = -strel_h / 2; j <= strel_h / 2; j++) 
				{
					for (int i = -strel_w / 2; i <= strel_w / 2; i++)
					{
						if (y + j < 0 || y + j > _height - 1)
							continue;
						if (x + i < 0 || x + i > _width - 1)
							continue;

						val2 = trg._i8I[(j*_width)+i];
						if (val > val2)
							val = val2;
					}
				}


				_i8I[(y*_width)+x] = val;
			}
		}

		return true;
	}

	protected boolean dilate(int iEw, int iEh)
	{
		int w = iEw, h= iEh;

		if (w < 2) w = 2;
		if (h < 2) h = 2;

		int strel_w = w;
		int strel_h = h;

		short val, val2;

		Buffer8I trg = new Buffer8I();
		if(!trg.createFrom(this))
			return false;

		for(int y=0; y<_height; y++)
		{
			for(int x=0; x<_width; x++)
			{
				val = 0;
				for (int j = -strel_h / 2; j <= strel_h / 2; j++) 
				{
					for (int i = -strel_w / 2; i <= strel_w / 2; i++)
					{
						if (y + j < 0 || y + j > _height - 1)
							continue;
						if (x + i < 0 || x + i > _width - 1)
							continue;

						val2 = trg._i8I[(j*_width)+i];
						if (val < val2)
							val = val2;
					}
				}

				_i8I[(y*_width)+x] = val;
			}
		}
		
		return true;
	}
	
	public boolean applyKernel3x3(float[] kernel, float divider, float displace) {
		short pNew[] = Kernel.do3x3_i8(kernel, divider, displace, _width, _height, _i8I);
		
		if(pNew == null) return false;
		
		_i8I = pNew;
		
		return true;
	}
}
